{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "24fa8b17",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import easydict\n",
    "import pickle\n",
    "import cv2\n",
    "import numpy as np\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "from matplotlib import pyplot as plt\n",
    "from pcdet.utils.kmeans import kmeans, kmeans_predict\n",
    "# from pcdet.utils.prototype import save_prototype, load_prototype, prototype_update\n",
    "# from pcdet.models.model_utils import centernet_utils"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3823299b",
   "metadata": {},
   "outputs": [],
   "source": [
    "a = torch.tensor(["
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e8695563",
   "metadata": {},
   "outputs": [],
   "source": [
    "info_path = 'nuscenes_infos_10sweeps_train_sparse.pkl'\n",
    "with open(info_path, 'rb') as f:\n",
    "    infos = pickle.load(f)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "369bc670",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "28130"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(infos)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "905fc682",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array(['pedestrian', 'car', 'traffic_cone', 'truck'], dtype='<U12')"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "infos[5]['gt_names']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bd4e907d",
   "metadata": {},
   "outputs": [],
   "source": [
    "prototype_path = './_centerpoint_kitti_prototype_features.pth'\n",
    "data = torch.load(prototype_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e53f4eb5",
   "metadata": {},
   "outputs": [],
   "source": [
    "ckpt_path = './checkpoint_epoch_20.pth'\n",
    "ckpt = torch.load(ckpt_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "556f519f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor(0.)"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ckpt['model_state']['dense_head.logit_scale']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "ff3b8af8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([1, 1, 1, 3, 4, 1, 1, 3, 2, 4, 3, 3, 3, 3, 1, 2, 3, 3, 1, 3, 4, 3, 3, 1,\n",
       "        2, 3, 3, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 3, 1, 3, 3, 4, 3, 1, 1,\n",
       "        3, 3, 3, 1, 3, 3, 3, 3, 1, 3, 3, 3, 1, 2, 1, 1, 1, 2, 1, 2, 2, 1, 2, 2,\n",
       "        2, 4, 4, 1, 1, 2, 2, 2, 1, 2, 1, 2, 1, 4, 1, 1, 1, 2, 4, 2, 2, 2, 2, 2,\n",
       "        2, 2, 1, 1, 2, 2, 4, 1, 1, 2, 1, 1, 1, 4, 1, 2, 4, 2, 1, 4, 2, 2, 1, 1,\n",
       "        1, 2, 2, 4, 1, 4, 2, 1, 2, 2, 2, 1, 1, 1, 2, 2, 1, 4, 2, 1, 2, 1, 2, 2,\n",
       "        1, 1, 2, 2, 2, 2, 1, 1, 2, 1, 2, 1, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1, 4, 1,\n",
       "        2, 2, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 4, 1, 2, 1, 2,\n",
       "        2, 2, 1, 1, 1, 2, 2, 2, 1, 2, 1, 2, 1, 2, 2, 2, 1, 2, 2, 1, 1, 1, 2, 2,\n",
       "        2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 3, 5, 3, 2, 3,\n",
       "        5, 4, 5, 5, 2, 5, 3, 3, 5, 3, 3, 3, 2, 5, 5, 5, 3, 3, 3, 5, 5, 3, 3, 3,\n",
       "        4, 3, 5, 3, 3, 4, 3, 3, 5, 5, 5, 3, 3, 5, 5, 5, 3, 3, 3, 4, 5, 5, 4, 5,\n",
       "        5, 3, 5, 4, 3, 3, 5, 5, 3, 5, 2, 3, 5, 3, 5, 3, 3, 4, 4, 4, 4, 4, 4, 4,\n",
       "        3, 3, 3, 2, 3, 3, 5, 5, 5, 5, 4, 5, 3, 5, 3, 3, 3, 3, 5, 3, 5, 5, 4, 5,\n",
       "        4, 5, 3, 5, 4, 4, 5, 3, 3, 3, 5, 3, 4, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,\n",
       "        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 5,\n",
       "        3, 5, 3, 3, 3, 3, 3, 3, 5, 5, 5, 5, 3, 3, 3, 5, 3, 4, 3, 3, 3, 4, 3, 3,\n",
       "        2, 4, 5, 5, 5, 3, 5, 3, 3, 3, 3, 5, 5, 3, 5, 4, 3, 4, 5, 5, 3, 3, 2, 5,\n",
       "        5, 3, 5, 3, 5, 3, 3, 3, 5, 5, 3, 5, 3, 3, 3, 2, 5, 5, 3, 5, 3, 3, 3, 2,\n",
       "        3, 3, 3, 5, 5, 3, 5, 3, 5, 5, 3, 3, 3, 5, 2, 3, 5, 5, 5, 3, 3, 2, 3, 2,\n",
       "        3, 3, 5, 5, 3, 3, 3, 3, 5, 5, 3, 3, 3, 4, 5, 5, 3, 5, 5, 3])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ckpt['model_state']['dense_head.bank_labels'][2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "1e9b0748",
   "metadata": {},
   "outputs": [],
   "source": [
    "proto_features = ckpt['model_state']['dense_head.proto_features']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f4c30122",
   "metadata": {},
   "outputs": [],
   "source": [
    "data['proto_features'].shape, data['feature_bank'].shape, data['feature_count']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9bbbadad",
   "metadata": {},
   "outputs": [],
   "source": [
    "num_class, num_prototype, feature_dim = data['proto_features'].shape\n",
    "feat2proto, new_proto = kmeans(data['feature_bank'][0], num_prototype, distance='cosine', norm=False)  # (num_prototype, feature_dim)\n",
    "feat2proto_count = torch.bincount(feat2proto, minlength=num_prototype)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "7ea508c4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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uqXBgwerUKFC8vX1VZs2bXT69GmHvahWrZoKFCigb775xuFyPJw44wtTS33zf+SRR6xjiYmJatasmerWraupU6fK29tbhmGoTZs22rBhg3r16qWqVatq9erVGj58uM6cOaN3331XkvSf//xHvXv3Vo0aNdS3b19JUunSpSWl/MCoV6+e/Pz89Morr8jd3V3vv/++GjRooB9//FE1a9bUk08+qQEDBmjmzJl69dVXFRoaKkkKDQ1V165d9emnn2r16tV6+umnrfVGR0dr/fr1Gjt2rM1z+/HHH/XFF19owIAB8vT01Ny5c9W8eXPt2LFDlSpVkiSdP39eTzzxhDUIFipUSP/973/Vq1cvxcbGatCgQffsYUJCgs0PKEny9vaWt7e34uLiVL9+fZ05c0b/+te/VKJECW3dulWjRo3SuXPnNH36dOs2M2bMUJs2bRQeHq74+HgtWbJEzz77rL777ju1atXqnv3NqPXr1+vLL79URESEChYsqODg4HT348MPP9SAAQPUoUMHDRw4UDdv3tTevXu1fft2denSJc1jhoWF6aWXXtLixYsVFhZms2zx4sUqWbKkzfzRO7Vv31779+9X//79FRwcrAsXLmjt2rWKiopyGPrv5vjx41q+fLmeffZZPfroozp//rzef/991a9fX3/88YeKFi2a7n3961//UuPGjW3GVq1apUWLFqlw4cKSpNjYWH300Ufq3Lmz+vTpo6tXr+rjjz9Ws2bNtGPHDlWtWlWFChXSe++9p5deeknPPPOMtUePPfZYmsfu3bu3PvnkE3Xo0EFDhw7V9u3bNWnSJB04cEBff/21zbpHjx5Vhw4d1KtXL3Xv3l3z589Xjx49VK1aNVWsWDHNYyQkJOiXX37RSy+9lO6e3P7eEhcXp3Xr1unJJ59UiRIl0r2Pzz//XD4+Pnr66aeVJ08elS5dWosWLVLt2rXTvY87pQbx/PnzW8f279+vOnXqqFixYho5cqR8fHz05Zdfql27dlq2bJmeeeYZSSnvNQ0bNlRiYqJ1vQ8++EB58uS573quXbumevXq6cCBA3rhhRf0j3/8Q5cuXdKKFSt0+vRpFSxYUMnJyWrTpo22bNmivn37KjQ0VPv27dO7776rw4cP20x96t27tz777DN16dJFtWvX1vr1663vH4784x//4MOKZmMAJrBgwQJDkvHDDz8YFy9e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qNFdXx+pr4djx47ZnOV1VF+qEydO2PUYD7eceVoLyGFatmyppKQkzZ4922b83XfflcViUYsWLaxjPj4+dm/4rq6uatq0qb755hubS0+dP39eixcvVt26da2/Dk29ZrCjECelzPOzWCwaOHCgjh8/nuYPv23bttnMnTx16pS++eYbNW3aVK6urnJ1dVX79u21bNkyhz94Ll68mGY/0qtjx47atm2bwzmJMTExSkxMlJTSH4vFYnM26M8//3R4owpH/ZVSfjheuXJFe/futY6dO3fO7pP9aclIP1LnfKby8PBQhQoVZBiGw8vN3alOnToKDg7WZ599pi+++EL169e3m15zp7i4OLubDpQuXVq+vr42U0NKly5td9m6Dz74wO5Mm6urq92Zzq+++uq+7up17tw5dezYUXXr1tU777zjcJ3Us623H3P79u3atm2bzXre3t6S0n79365ly5aSZHN1EEnWM4PpnQ5wL+7u7qpevbp+/fXX+97H2LFjZRiGunbtqmvXrtkt37lzpz755BNJ/5vmMHz4cHXo0MHmq0+fPgoJCbmvm1lIUoMGDVSjRg1Nnz5dN2/eVOHChdWgQQO9//77dqFUsn3dt2zZUj///LN27Nhhs9xRLel9HbZv31579uxx+O809bXSsWNHnTlzRh9++KHdOjdu3ND169clyfo+PHPmTJt17nx93G7Xrl0PdJUM5D6c8QXSoXXr1mrYsKFee+01/fnnn6pSpYrWrFmjb775RoMGDbKZU1atWjX98MMP1gvXP/roo6pZs6YmTJigtWvXqm7dunr55Zfl5uam999/X7du3bL5NXfVqlXl6uqqyZMn68qVK/L09NRTTz1lvSxUoUKF1Lx5c3311Vfy9/dP84d7pUqV1KxZM5vLmUmy+VX822+/rQ0bNqhmzZrq06ePKlSooMuXL2vXrl364YcfdPny5Qfq2/Dhw7VixQo9/fTT1stGXb9+Xfv27dPSpUv1559/qmDBgmrVqpWmTZum5s2bq0uXLrpw4YLmzJmjMmXK2ATZu/W3U6dOGjFihJ555hkNGDBAcXFxeu+991S2bNl0f3gqvf1o2rSpAgICVKdOHRUpUkQHDhzQ7Nmz1apVq3R9gMlisahLly6aOHGipJS7a93L4cOH1ahRI3Xs2FEVKlSQm5ubvv76a50/f16dOnWyrte7d2+9+OKLat++vZo0aaI9e/Zo9erVdmdxn376ab3xxhvq2bOnateurX379mnRokU2H75MrwEDBujixYt65ZVXtGTJEptljz32mB577DE9/fTTioyM1DPPPKNWrVrpxIkTmjdvnipUqGATBPPkyaMKFSroiy++UNmyZVWgQAFVqlTJegm+21WpUkXdu3fXBx98oJiYGNWvX187duzQJ598onbt2qlhw4YZfi5padu2rV577TXFxsZa/5OaEbVr19acOXP08ssvq3z58uratatCQkJ09epVbdy4UStWrNCECRN069YtLVu2TE2aNEnzNwdt2rTRjBkzdOHCBev7QkYMHz5czz77rBYuXKgXX3xRc+bMUd26dVW5cmX16dNHpUqV0vnz57Vt2zadPn3aem3nV155Rf/5z3/UvHlzDRw40Ho5s5IlS9r9O03v63D48OFaunSpnn32Wb3wwguqVq2aLl++rBUrVmjevHmqUqWKunbtqi+//FIvvviiNmzYoDp16igpKUkHDx7Ul19+qdWrV6t69eqqWrWqOnfurLlz5+rKlSuqXbu21q1bZ50jfaedO3fq8uXLatu2bYZ7iFwsG64kAWS51Ev7/PLLL3ddL/XSRI5cvXrVGDx4sFG0aFHD3d3dCAkJMd555x3rJXdSHTx40HjyySeNPHnyGJJsLt2za9cuo1mzZkbevHkNb29vo2HDhsbWrVvtjvXhhx8apUqVMlxdXR1e2uzLL780JBl9+/Z1WKsko1+/fsZnn31mhISEGJ6ensbjjz9utx/DMIzz588b/fr1M4KCggx3d3cjICDAaNSokfHBBx/ctVeGkXJpolatWt11natXrxqjRo0yypQpY3h4eBgFCxY0ateubUydOtWIj4+3rvfxxx9bay1fvryxYMECh5fNult/16xZY1SqVMnw8PAwypUrZ3z22WdpXs6sX79+DutNTz/ef/9948knnzQeeeQRw9PT0yhdurQxfPhwm0uU3cv+/fsNSYanp6fx999/2y2/8zJily5dMvr162eUL1/e8PHxMfLly2fUrFnT+PLLL222S0pKMkaMGGEULFjQ8Pb2Npo1a2YcPXrU4eXMhg4dagQGBhp58uQx6tSpY2zbts2oX7++Ub9+/TTrMAz7y4rVr1/f4WXtdNslpJKTk42JEycaJUuWtL4ev/vuO4eXodu6datRrVo1w8PDw2Yfjv4uExISjPHjxxuPPvqo4e7ubgQFBRmjRo2yuTSXYaT9Wr3z+abl/Pnzhpubm/Gf//zHZvxu7xmO7Ny50+jSpYv1fSR//vxGo0aNjE8++cRISkoyli1bZkgyPv744zT3sXHjRkOSMWPGjDTXudt7XlJSklG6dGmjdOnS1svNHTt2zOjWrZsREBBguLu7G8WKFTOefvppY+nSpTbb7t2716hfv77h5eVlFCtWzHjzzTeNjz/+2O5yZul9HRqGYfz1119GRESEUaxYMcPDw8MoXry40b17d5vLCsbHxxuTJ082KlasaHh6ehr58+c3qlWrZowfP97m392NGzeMAQMGGI888ojh4+NjtG7d2jh16pTDy5mNGDHCKFGihN17OB5uFsPgJtVAbvPNN9+oXbt22rRpk83lmVJZLBb169fPbmoGgPvXq1cvHT58WJs3b87uUvCAbt26peDgYI0cOVIDBw7M7nKQhZjjC+RCH374oUqVKqW6detmdymAaYwdO1a//PILd/p6CCxYsEDu7u568cUXs7sUZDHm+AK5yJIlS7R37159//33mjFjBteeBLJQiRIl7D5giNzpxRdfJPSaFMEXyEU6d+6svHnzqlevXnr55ZezuxwAAHIV5vgCAADAFHLdHN85c+YoODhYXl5eqlmzps31BB2JiYlRv379FBgYKE9PT5UtW9Z6P28AAACYR66a6vDFF19oyJAhmjdvnmrWrKnp06erWbNmOnTokMNrGcbHx6tJkyYqXLiwli5dar3XuL+/f9YXDwAAgGyVq6Y61KxZU//85z+tl2hKTk5WUFCQ+vfvr5EjR9qtP2/ePL3zzjs6ePCg3N3d7+uYycnJOnv2rHx9ffkgEQAAQA5kGIauXr2qokWLysUl7QkNuSb4xsfHy9vbW0uXLlW7du2s4927d1dMTIy++eYbu21atmypAgUKyNvbW998840KFSqkLl26aMSIEdbbZ97p1q1bNrf/PHPmjCpUqJDpzwcAAACZ69SpU3e9BXyumepw6dIlJSUlqUiRIjbjRYoU0cGDBx1uc/z4ca1fv17h4eFauXKljh49qpdfflkJCQkaO3asw20mTZpkc0vXVB999JH1HvIAAADIOeLi4tS7d+973jY+15zxPXv2rIoVK6atW7eqVq1a1vFXXnlFP/74o7Zv3263TdmyZXXz5k2dOHHCeoZ32rRp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UlaV7XT305fatWtr586d1ukQx48f18qVK9WyZcssqTknMsN7bXrlrp+s2SA6OlqFCxe2GXNzc1OBAgUUHR3tcJvjx49LksaNG6epU6eqatWq+vTTT9WoUSP9/vvvCgkJcXrdznY/fZGkwYMHq3bt2mrbtq2zS8xy99uT2126dElvvvmm+vbt64wSnerSpUtKSkpSkSJFbMaLFCmigwcPOtwmOjra4frp7VducD99udOIESNUtGhRux9cudX99GTLli36+OOPtXv37iyoMOvdT0+OHz+u9evXKzw8XCtXrtTRo0f18ssvKyEhQWPHjs2Ksp3ufvrSpUsXXbp0SXXr1pVhGEpMTNSLL76oV199NStKzpHSeq+NjY3VjRs3lCdPnmyqLOuZ9ozvyJEjZbFY7vqV3h9Kd0pOTpYk/etf/1LPnj31+OOP691331W5cuU0f/78zHwamc6ZfVmxYoXWr1+v6dOnZ27RTubMntwuNjZWrVq1UoUKFTRu3LgHLxwPhbfffltLlizR119/LS8vr+wuJ1tcvXpVXbt21YcffqiCBQtmdzk5RnJysgoXLqwPPvhA1apV03PPPafXXntN8+bNy+7SstXGjRs1ceJEzZ07V7t27VJkZKS+//57vfnmm9ldGnIA057xHTp0qHr06HHXdUqVKqWAgABduHDBZjwxMVGXL19O81f1gYGBkqQKFSrYjIeGhioqKur+i84CzuzL+vXrdezYMfn7+9uMt2/fXvXq1dPGjRsfoHLncWZPUl29elXNmzeXr6+vvv76a7m7uz9o2VmuYMGCcnV11fnz523Gz58/n+bzDwgIyND6udH99CXV1KlT9fbbb+uHH37QY4895swys1RGe3Ls2DH9+eefat26tXUs9QSDm5ubDh06pNKlSzu3aCe7n9dJYGCg3N3d5erqah0LDQ1VdHS04uPj5eHh4dSas8L99GXMmDHq2rWrevfuLUmqXLmyrl+/rr59++q1116Ti4v5zvml9V7r5+dnqrO9komDb6FChVSoUKF7rlerVi3FxMRo586dqlatmqSUAJecnKyaNWs63CY4OFhFixbVoUOHbMYPHz6sFi1aPHjxTuTMvowcOdL6RpSqcuXKevfdd21+oOU0zuyJlHKmt1mzZvL09NSKFSty7Vk9Dw8PVatWTevWrbNeZio5OVnr1q1TRESEw21q1aqldevWadCgQdaxtWvXqlatWllQcda4n75I0pQpU/TWW29p9erVNvPGHwYZ7Un58uW1b98+m7HRo0fr6tWrmjFjhoKCgrKibKe6n9dJnTp1tHjxYiUnJ1vD3OHDhxUYGPhQhF7p/voSFxdnF25T/3NgGIZT682patWqZXeZu4ftvTbdsvvTdblB8+bNjccff9zYvn27sWXLFiMkJMTmElWnT582ypUrZ2zfvt069u677xp+fn7GV199ZRw5csQYPXq04eXlZRw9ejQ7noJT3E9f7qSH6KoOhpHxnly5csWoWbOmUblyZePo0aPGuXPnrF+JiYnZ9TTu25IlSwxPT09j4cKFxh9//GH07dvX8Pf3N6Kjow3DMIyuXbsaI0eOtK7/008/GW5ubsbUqVONAwcOGGPHjn1oL2eWkb68/fbbhoeHh7F06VKb18TVq1ez6ylkuoz25E4P41UdMtqTqKgow9fX14iIiDAOHTpkfPfdd0bhwoWNCRMmZNdTcIqM9mXs2LGGr6+v8fnnnxvHjx831qxZY5QuXdro2LFjdj2FTHf16lXjt99+M3777TdDkjFt2jTjt99+M06ePGkYhmGMHDnS6Nq1q3X91MuZDR8+3Dhw4IAxZ84cLmeGtP31119G586djbx58xp+fn5Gz549bX4AnThxwpBkbNiwwWa7SZMmGcWLFze8vb2NWrVqGZs3b87iyp3rfvtyu4ct+Ga0Jxs2bDAkOfw6ceJE9jyJBzRr1iyjRIkShoeHh1GjRg3j559/ti6rX7++0b17d5v1v/zyS6Ns2bKGh4eHUbFiReP777/P4oqzRkb6UrJkSYevibFjx2Z94U6U0dfK7R7G4GsYGe/J1q1bjZo1axqenp5GqVKljLfeeitX/qf5XjLSl4SEBGPcuHFG6dKlDS8vLyMoKMh4+eWXjb///jvrC3eStH52pPahe/fuRv369e22qVq1quHh4WGUKlXKWLBgQZbXnRNYDMOk5/0BAABgKuab4Q0AAABTIvgCAADAFAi+AAAAMAWCLwAAAEyB4AsAAABTIPgCAADAFAi+AAAAMAWCLwAAAEyB4AsAAABTIPgCMLUePXrIYrHIYrHIw8NDZcqU0RtvvKHExETrOoZh6IMPPlDNmjWVN29e+fv7q3r16po+fbri4uJs9nf69Gl5eHioUqVK6a4hOjpa/fv3V6lSpeTp6amgoCC1bt1a69aty7Tn+TDo0aOH2rVrd8/1Nm3apNatW6to0aKyWCxavny502sDkDsQfAGYXvPmzXXu3DkdOXJEQ4cO1bhx4/TOO+9Yl3ft2lWDBg1S27ZttWHDBu3evVtjxozRN998ozVr1tjsa+HCherYsaNiY2O1ffv2ex77zz//VLVq1bR+/Xq988472rdvn1atWqWGDRuqX79+mf5czeD69euqUqWK5syZk92lAMhpDAAwse7duxtt27a1GWvSpInxxBNPGIZhGF988YUhyVi+fLndtsnJyUZMTIzN41KlShmrVq0yRowYYfTp0+eex2/RooVRrFgx49q1a3bL/v77b+v3J0+eNNq0aWP4+PgYvr6+xrPPPmtER0dbl48dO9aoUqWK8fHHHxtBQUGGj4+P8dJLLxmJiYnG5MmTjSJFihiFChUyJkyYYHMMScbcuXON5s2bG15eXsajjz5qfPXVVzbr7N2712jYsKHh5eVlFChQwOjTp49x9epVux6+8847RkBAgFGgQAHj5ZdfNuLj463r3Lx50xg6dKhRtGhRw9vb26hRo4axYcMG6/IFCxYY+fLlM1atWmWUL1/e8PHxMZo1a2acPXvW+vwk2Xzdvn1aJBlff/31PdcDYA6c8QWAO+TJk0fx8fGSpEWLFqlcuXJq27at3XoWi0X58uWzPt6wYYPi4uLUuHFjPf/881qyZImuX7+e5nEuX76sVatWqV+/fvLx8bFb7u/vL0lKTk5W27ZtdfnyZf34449au3atjh8/rueee85m/WPHjum///2vVq1apc8//1wff/yxWrVqpdOnT+vHH3/U5MmTNXr0aLsz0WPGjFH79u21Z88ehYeHq1OnTjpw4ICklLOnzZo1U/78+fXLL7/oq6++0g8//KCIiAibfWzYsEHHjh3Thg0b9Mknn2jhwoVauHChdXlERIS2bdumJUuWaO/evXr22WfVvHlzHTlyxLpOXFycpk6dqv/85z/atGmToqKiNGzYMEnSsGHD1LFjR+vZ+XPnzql27dpp9hYAHMru5A0A2en2M77JycnG2rVrDU9PT2PYsGGGYRhGaGio0aZNm3Ttq0uXLsagQYOsj6tUqWIsWLAgzfW3b99uSDIiIyPvut81a9YYrq6uRlRUlHVs//79hiRjx44dhmGknBH19vY2YmNjres0a9bMCA4ONpKSkqxj5cqVMyZNmmR9LMl48cUXbY5Xs2ZN46WXXjIMwzA++OADI3/+/DZnpL///nvDxcXFesa5e/fuRsmSJY3ExETrOs8++6zx3HPPGYaRcrba1dXVOHPmjM1xGjVqZIwaNcowjJQzvpKMo0ePWpfPmTPHKFKkiPWxo7Pz9yLO+AK4jVu2pm4AyAG+++475c2bVwkJCUpOTlaXLl00btw4SSkfbEuPmJgYRUZGasuWLdax559/Xh9//LF69OjhcJv07vvAgQMKCgpSUFCQdaxChQry9/fXgQMH9M9//lOSFBwcLF9fX+s6RYoUkaurq1xcXGzGLly4YLP/WrVq2T3evXu39dhVqlSxOSNdp04dJScn69ChQypSpIgkqWLFinJ1dbWuExgYqH379kmS9u3bp6SkJJUtW9bmOLdu3dIjjzxifezt7a3SpUvb7OPOWgHgQRB8AZhew4YN9d5778nDw0NFixaVm9v/3hrLli2rgwcP3nMfixcv1s2bN1WzZk3rmGEYSk5O1uHDh+1CnySFhITIYrGka//p4e7ubvPYYrE4HEtOTs6U493r2KnHuXbtmlxdXbVz506bcCxJefPmves+0vufAwBID+b4AjA9Hx8flSlTRiVKlLAJvZLUpUsXHT58WN98843ddoZh6MqVK5Kkjz/+WEOHDtXu3butX3v27FG9evU0f/58h8ctUKCAmjVrpjlz5jicCxwTEyNJCg0N1alTp3Tq1Cnrsj/++EMxMTGqUKHC/T5tq59//tnucWhoqPXYe/bssanvp59+kouLi8qVK5eu/T/++ONKSkrShQsXVKZMGZuvgICAdNfp4eGhpKSkdK8PAHci+ALAXXTs2FHPPfecOnfurIkTJ+rXX3/VyZMn9d1336lx48bWy5vt2rVLvXv3VqVKlWy+OnfurE8++cTmusC3mzNnjpKSklSjRg0tW7ZMR44c0YEDBzRz5kzrFITGjRurcuXKCg8P165du7Rjxw5169ZN9evXV/Xq1R/4OX711VeaP3++Dh8+rLFjx2rHjh3WD6+Fh4fLy8tL3bt31++//64NGzaof//+6tq1q3Waw72ULVtW4eHh6tatmyIjI/V/7d2vqypBGMbxZ+tpBqvIlhWRhQWtiqgYVTDb/Ae0iyabSTCJYPBHUoOCwWhREbGZBKOIyWa4t1muXDxwyz3z/eTZmXfbMy/DzPl81mazUbPZ1Hw+/7jOYDCo4/Go0+mk2+2m5/P5dtzj8XhtPiTpfD7rcDjocrl8vBaAn4ngCwB/YVmWBoOBWq2WptOpEomEXNdVvV5XLpdTNptVt9tVOBxWKBT64/tCoaDr9arFYvF2ftu2td/vlUwmVa1WFYlElMlktFqt1Ol0XjXMZjP5fD7F43Gl02nZtq3xePxP/rHRaGg0Gsl1XfX7fQ2Hw1cn+evrS8vlUvf7XbFYTMViUalUSu12+1tr9Ho9lUolVatVOY6jfD6v7XarQCDw8RzlclmO4ygajcrv92u9Xr8dt9vt5HmePM+TJFUqFXmep1qt9q2aAfw81i8OUAGAsSzL0mQy+ehFNAD439HxBQAAgBEIvgAAADAC15kBgME47QbAJHR8AQAAYASCLwAAAIxA8AUAAIARCL4AAAAwAsEXAAAARiD4AgAAwAgEXwAAABiB4AsAAAAj/AYMRVpYekWzeAAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 800x800 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 可视化原型特征\n",
    "proto_features = proto_features.cpu().numpy()\n",
    "num_class, num_prototype, feature_dim = proto_features.shape\n",
    "# pca降维到2维\n",
    "from sklearn.decomposition import PCA\n",
    "pca = PCA(n_components=2)\n",
    "proto_features_2d = pca.fit_transform(proto_features.reshape(-1, feature_dim))\n",
    "proto_features_2d = proto_features_2d.reshape(num_class, num_prototype, 2)\n",
    "\n",
    "colors = ['r', 'g', 'b', 'c', 'm', 'y', 'k']\n",
    "plt.figure(figsize=(8, 8))\n",
    "for cls_idx in range(num_class):\n",
    "    plt.scatter(proto_features_2d[cls_idx, :, 0], proto_features_2d[cls_idx, :, 1], \n",
    "                c=colors[cls_idx % len(colors)], label=f'Class {cls_idx}')\n",
    "    for proto_idx in range(num_prototype):\n",
    "        plt.text(proto_features_2d[cls_idx, proto_idx, 0], proto_features_2d[cls_idx, proto_idx, 1], \n",
    "                 str(proto_idx), fontsize=9)\n",
    "plt.title('Prototype Features Visualization (PCA Reduced)')\n",
    "plt.xlabel('PCA Component 1')\n",
    "plt.ylabel('PCA Component 2')\n",
    "plt.legend()\n",
    "plt.grid()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8dd95e7d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Test KMeans clustering\n",
    "x = torch.randn(100, 2)\n",
    "x = x / x.norm(dim=-1, keepdim=True)  # Normalize to unit sphere\n",
    "labels, centroids = kmeans(x, num_clusters=5, distance='cosine', norm=False)\n",
    "labels2 = kmeans_predict(x, centroids)\n",
    "plt.scatter(x[:, 0], x[:, 1], c=labels, cmap='viridis')\n",
    "plt.scatter(centroids[:, 0], centroids[:, 1], color='red', marker='x')\n",
    "plt.title('KMeans Clustering')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a0e13bf8",
   "metadata": {},
   "outputs": [],
   "source": [
    "prototype_cfg = easydict.EasyDict({\n",
    "    'PATH': 'test/prototype.pth',\n",
    "    'NUM_CLASS': 3,\n",
    "    'NUM_PROTO': 5,\n",
    "    'BANK_SIZE': 20,\n",
    "    'FEATURE_DIM': 2\n",
    "})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7c66a2de",
   "metadata": {},
   "outputs": [],
   "source": [
    "proto_features, feature_bank, feature_count = load_prototype(prototype_cfg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a96532fb",
   "metadata": {},
   "outputs": [],
   "source": [
    "save_prototype(proto_features, feature_bank, feature_count, prototype_cfg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b2e2f2bc",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_features = [torch.randn(3, 2).cuda() for _ in range(prototype_cfg.NUM_CLASS)]\n",
    "new_features = [new_features[i] / new_features[i].norm(dim=-1, keepdim=True) for i in range(len(new_features))]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a9d8b9e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "proto_features, feature_bank, feature_count, feat2proto_count = \\\n",
    "        prototype_update(proto_features, feature_bank, feature_count, new_features, prototype_cfg.NUM_PROTO)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7535b8a3",
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(8, 6))\n",
    "class_id = 0\n",
    "plt.scatter(feature_bank[class_id, :feature_count[class_id], 0].cpu(), \n",
    "            feature_bank[class_id, :feature_count[class_id], 1].cpu(), label='Feature Bank', alpha=0.5)\n",
    "plt.scatter(new_features[class_id][:, 0].cpu(), new_features[class_id][:, 1].cpu(), label='New Features', alpha=0.5)\n",
    "plt.scatter(proto_features[class_id].cpu()[:, 0], proto_features[class_id].cpu()[:, 1], label='Prototypes', color='red', marker='x', s=100)\n",
    "plt.title(f'Prototype Update for Class {class_id}')\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fb1afcd0",
   "metadata": {},
   "outputs": [],
   "source": [
    "def assign_target_of_single_head(num_classes, gt_boxes, feature_map_size, feature_map_stride, num_max_objs=500, gaussian_overlap=0.1, min_radius=2):\n",
    "    \"\"\"\n",
    "    Args:\n",
    "        gt_boxes: (N, 8)\n",
    "        feature_map_size: (2), [x, y]\n",
    "\n",
    "    Returns:\n",
    "\n",
    "    \"\"\"\n",
    "    heatmap = gt_boxes.new_zeros(num_classes, feature_map_size[1], feature_map_size[0])\n",
    "    ret_boxes = gt_boxes.new_zeros((num_max_objs, gt_boxes.shape[-1] - 1 + 1))\n",
    "    inds = gt_boxes.new_zeros(num_max_objs).long()\n",
    "    mask = gt_boxes.new_zeros(num_max_objs).long()\n",
    "    ret_boxes_src = gt_boxes.new_zeros(num_max_objs, gt_boxes.shape[-1])\n",
    "    ret_boxes_src[:gt_boxes.shape[0]] = gt_boxes\n",
    "\n",
    "    x, y, z = gt_boxes[:, 0], gt_boxes[:, 1], gt_boxes[:, 2]\n",
    "    coord_x = x / feature_map_stride\n",
    "    coord_y = y / feature_map_stride\n",
    "    coord_x = torch.clamp(coord_x, min=0, max=feature_map_size[0] - 0.5)  # bugfixed: 1e-6 does not work for center.int()\n",
    "    coord_y = torch.clamp(coord_y, min=0, max=feature_map_size[1] - 0.5)  #\n",
    "    center = torch.cat((coord_x[:, None], coord_y[:, None]), dim=-1)\n",
    "    center_int = center.int()\n",
    "    center_int_float = center_int.float()\n",
    "\n",
    "    dx, dy, dz = gt_boxes[:, 3], gt_boxes[:, 4], gt_boxes[:, 5]\n",
    "    dx = dx / feature_map_stride\n",
    "    dy = dy / feature_map_stride\n",
    "\n",
    "    radius = centernet_utils.gaussian_radius(dx, dy, min_overlap=gaussian_overlap)\n",
    "    radius = torch.clamp_min(radius.int(), min=min_radius)\n",
    "\n",
    "    for k in range(min(num_max_objs, gt_boxes.shape[0])):\n",
    "        if dx[k] <= 0 or dy[k] <= 0:\n",
    "            continue\n",
    "\n",
    "        if not (0 <= center_int[k][0] <= feature_map_size[0] and 0 <= center_int[k][1] <= feature_map_size[1]):\n",
    "            continue\n",
    "\n",
    "        cur_class_id = (gt_boxes[k, -1] - 1).long()\n",
    "        centernet_utils.draw_gaussian_to_heatmap(heatmap[cur_class_id], center[k], radius[k].item())\n",
    "\n",
    "        inds[k] = center_int[k, 1] * feature_map_size[0] + center_int[k, 0]\n",
    "        mask[k] = 1\n",
    "\n",
    "        ret_boxes[k, 0:2] = center[k] - center_int_float[k].float()\n",
    "        ret_boxes[k, 2] = z[k]\n",
    "        ret_boxes[k, 3:6] = gt_boxes[k, 3:6].log()\n",
    "        ret_boxes[k, 6] = torch.cos(gt_boxes[k, 6])\n",
    "        ret_boxes[k, 7] = torch.sin(gt_boxes[k, 6])\n",
    "        if gt_boxes.shape[1] > 8:\n",
    "            ret_boxes[k, 8:] = gt_boxes[k, 7:-1]\n",
    "\n",
    "    return heatmap, ret_boxes, inds, mask, ret_boxes_src"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bd784bef",
   "metadata": {},
   "outputs": [],
   "source": [
    "def prototype_learning(x, target_dict, proto_features, feat2proto_count, new_features, new_features_labels):\n",
    "    \"\"\"\n",
    "    Args:\n",
    "        x: (B, C, H, W)\n",
    "        target_dict:\n",
    "            'heatmaps': list of (B, num_classes, H, W)\n",
    "            'target_boxes': list of (B, M, 8)\n",
    "            'inds': list of (B, M)\n",
    "            'masks': list of (B, M)\n",
    "            'target_boxes_src': list of (B, M, 9)\n",
    "\n",
    "    Returns:\n",
    "\n",
    "    \"\"\"\n",
    "    # feature extraction\n",
    "    feats = F.normalize(x, p=2, dim=-1)\n",
    "\n",
    "    # compute cosine similarity and assign prototypes\n",
    "    cos_sim = (feats[:, :, :, None, None, None, :] @ proto_features[None, None, None, :, :, :, None]).squeeze(-1, -2)  # (B, H, W, NUM_CLASS, NUM_PROTO)\n",
    "    max_sim, max_proto_ids = cos_sim.max(dim=-1)  # (B, H, W, NUM_CLASS)\n",
    "    cos_sim_rect = cos_sim / (feat2proto_count + 1).log().clamp(min=1.0)[None, None, None, :, :]\n",
    "    max_sim_rect, max_proto_ids_rect = cos_sim_rect.max(dim=-1)  # (B, H, W, NUM_CLASS)\n",
    "    \n",
    "    for idx, cur_class_ids in enumerate([torch.tensor([0, 1, 2]).cuda()]):\n",
    "        heatmaps = target_dict['heatmaps'][idx]  # (B, num_classes, H, W)\n",
    "\n",
    "        # collect gt features for prototype updating\n",
    "        gt_inds = torch.where(heatmaps == 1)  # (4, M) (B, class_id, y, x)\n",
    "        gt_feats = feats[gt_inds[0], gt_inds[2], gt_inds[3], :]  # (M, D)\n",
    "        gt_class_ids = cur_class_ids[gt_inds[1]]  # (M,)\n",
    "        gt_proto_ids = max_proto_ids_rect[gt_inds[0], gt_inds[2], gt_inds[3], gt_class_ids]  # (M,)\n",
    "        for class_id in cur_class_ids:\n",
    "            class_mask = (gt_class_ids == class_id)\n",
    "            if class_mask.sum() == 0:\n",
    "                continue\n",
    "            class_feats = gt_feats[class_mask]  # (N_c, D)\n",
    "            new_features[class_id] = torch.cat([new_features[class_id], class_feats], dim=0)\n",
    "            new_features_labels[class_id] = torch.cat([new_features_labels[class_id], gt_proto_ids[class_mask]], dim=0)\n",
    "\n",
    "        # remove gt positions from max_sim to avoid duplicate counting\n",
    "        heatmap_inds = torch.where(heatmaps > 0)  # (4, N) (B, class_id, y, x)\n",
    "        max_sim[heatmap_inds[0], heatmap_inds[2], heatmap_inds[3], cur_class_ids[heatmap_inds[1]]] = 0\n",
    "\n",
    "    max_sim, max_class_ids = max_sim.max(dim=-1)  # (B, H, W)\n",
    "\n",
    "    # collect similar features for prototype updating\n",
    "    sim_mask = (max_sim > 0.3)\n",
    "    for class_id in range(3):\n",
    "        class_mask = (max_class_ids == class_id) & sim_mask  # (B, H, W)\n",
    "        if class_mask.sum() == 0:\n",
    "            continue\n",
    "        class_feats = feats[class_mask]  # (N_c, D)\n",
    "        new_features[class_id] = torch.cat([new_features[class_id], class_feats], dim=0)\n",
    "        new_features_labels[class_id] = torch.cat([new_features_labels[class_id], max_proto_ids_rect[class_mask][:, class_id]], dim=0)\n",
    "\n",
    "    # collect pseudo-labeled features and refine heatmaps\n",
    "    pseudo_mask = (max_sim > 0.3)\n",
    "    for idx, cur_class_ids in enumerate([torch.tensor([0, 1, 2]).cuda()]):\n",
    "        pseudo_heatmap = torch.zeros_like(target_dict['heatmaps'][idx])  # (B, num_classes, H, W)\n",
    "        for i, class_id in enumerate(cur_class_ids):\n",
    "            class_mask = (max_class_ids == class_id) & pseudo_mask  # (B, H, W)\n",
    "            pseudo_heatmap[:, i, :, :][class_mask] = max_sim[:, :, :][class_mask]\n",
    "        target_dict['heatmaps'][idx] = torch.clamp_max(target_dict['heatmaps'][idx] + pseudo_heatmap, max=1.0)\n",
    "\n",
    "    return target_dict, new_features, new_features_labels"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d8a72b5c",
   "metadata": {},
   "outputs": [],
   "source": [
    "prototype_cfg = easydict.EasyDict({\n",
    "    'PATH': 'prototype.pth',\n",
    "    'NUM_CLASS': 3,\n",
    "    'NUM_PROTO': 10,\n",
    "    'BANK_SIZE': 100,\n",
    "    'FEATURE_DIM': 128\n",
    "})\n",
    "proto_features, feature_bank, feature_count = load_prototype(prototype_cfg)\n",
    "feature_map_size = (30, 30)\n",
    "feature_map_stride = 1\n",
    "\n",
    "gt_boxes = torch.tensor([\n",
    "    [10.0, 15.0, 0.0, 4.0, 2.0, 1.5, 0.0, 1],\n",
    "    [5.0, 5.0, 0.0, 3.0, 3.0, 1.5, 1.57, 2],\n",
    "    [25.0, 20.0, 0.0, 5.0, 2.5, 1.5, -1.57, 3]\n",
    "]).cuda()\n",
    "\n",
    "heatmap, ret_boxes, inds, mask, ret_boxes_src = assign_target_of_single_head(\n",
    "    num_classes=3,\n",
    "    gt_boxes=gt_boxes,\n",
    "    feature_map_size=feature_map_size,\n",
    "    feature_map_stride=feature_map_stride\n",
    ")\n",
    "\n",
    "target_dict = {\n",
    "    'heatmaps': [heatmap.unsqueeze(0)],\n",
    "    'target_boxes': [ret_boxes.unsqueeze(0)],\n",
    "    'inds': [inds.unsqueeze(0)],\n",
    "    'masks': [mask.unsqueeze(0)],\n",
    "    'target_boxes_src': [ret_boxes_src.unsqueeze(0)]\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b419ab8c",
   "metadata": {},
   "outputs": [],
   "source": [
    "x = torch.randn(1, feature_map_size[1], feature_map_size[0], prototype_cfg.FEATURE_DIM).cuda()\n",
    "new_features = [torch.zeros((0, prototype_cfg.FEATURE_DIM)).float().cuda() for _ in range(prototype_cfg.NUM_CLASS)]\n",
    "new_features_labels = [torch.zeros((0,), dtype=torch.long).cuda() for _ in range(prototype_cfg.NUM_CLASS)]\n",
    "target_dict, new_features, new_features_labels = prototype_learning(\n",
    "    x,\n",
    "    target_dict,\n",
    "    proto_features,\n",
    "    feat2proto_count,\n",
    "    new_features,\n",
    "    new_features_labels\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a60cc057",
   "metadata": {},
   "outputs": [],
   "source": [
    "print([feat.shape for feat in new_features])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3c919767",
   "metadata": {},
   "outputs": [],
   "source": [
    "new_features = [torch.randn(40, 128).cuda() for _ in range(prototype_cfg.NUM_CLASS)]\n",
    "new_features = [new_features[i] / new_features[i].norm(dim=-1, keepdim=True) for i in range(len(new_features))]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1afe3ebc",
   "metadata": {},
   "outputs": [],
   "source": [
    "proto_features, feature_bank, feature_count, feat2proto_count = \\\n",
    "        prototype_update(proto_features, feature_bank, feature_count, new_features, prototype_cfg.NUM_PROTO)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6c0f81d7",
   "metadata": {},
   "outputs": [],
   "source": [
    "feat2proto_count"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f2e6aa52",
   "metadata": {},
   "outputs": [],
   "source": [
    "# display heatmaps using cv2\n",
    "heatmap = target_dict['heatmaps'][0][0]  # (num_classes, H, W)\n",
    "heatmap_np = heatmap.cpu().numpy().transpose(1, 2, 0)\n",
    "heatmap_vis = (heatmap_np * 255).astype(np.uint8)\n",
    "cv2.imshow('Heatmap', heatmap_vis)\n",
    "cv2.waitKey(0)\n",
    "# cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "86cf0888",
   "metadata": {},
   "outputs": [],
   "source": [
    "cv2.destroyAllWindows()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python310",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
